﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace LEV_Applications
{
    /// <summary>
    /// ref example from http://msdn.microsoft.com/zh-tw/library/fx6588te(v=vs.110).aspx
    /// </summary>
    public class ServerSocket : IDisposable
    {
        public static int ConnectionPort = 443;

        //默認信號為未發送狀態
        public static ManualResetEvent allDone = new ManualResetEvent(false);

        IPEndPoint gLocalEndPoint;
        Socket gListenerSocket;
        bool gListenerAcceptLoopFlag = false;

        public ServerSocket()
        {
            //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
            gLocalEndPoint = new IPEndPoint(IPAddress.Any, ConnectionPort);

            // 實體化 ListenerSocket
            // Create a TCP/IP socket.
            gListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            
            //log.Write("Server is started");
        }

        /// <summary>
        /// Implement IDisposable.
        /// </summary>
        public void Dispose()
        {
            try
            {
                gListenerAcceptLoopFlag = false;
                gListenerSocket.Shutdown(SocketShutdown.Both);
                gListenerSocket.Close();
            }
            catch
            {
            }
            //GC.SuppressFinalize(this);
        }

        public void StartListening()
        {
            ////建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
            //IPEndPoint gLocalEndPoint = new IPEndPoint(IPAddress.Any, ConnectionPort);
            //// 實體化 ListenerSocket
            //// Create a TCP/IP socket.
            //Socket gListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Bind socket to endpoint and listen for incoming connections.
            try
            {
                //socket連繫到該位址
                gListenerSocket.Bind(gLocalEndPoint);

                // 啟動監聽
                //backlog=4 參數會指定可在佇列中等候接收的輸入連接數。若要決定可指定的最大連接數，除非同時間的連線非常的大，否則值4應該很夠用。
                gListenerSocket.Listen(100);

                gListenerAcceptLoopFlag = true;

                while (gListenerAcceptLoopFlag)
                {
                    //ManualResetEvent必須要再呼叫Reset方法後才會處於未收到信號的狀態
                    // Set the event to nonsignaled state.
                    allDone.Reset();

                    // 以上完成了SERVER的Listening
                    // 之後BeginAccept()  開啟執行緒準備接收client的要求。
                    // 此處的BeginAccept 第一個參數就是當Socket一開始接收到Client的連線要求時，
                    // 立刻會呼叫delegate的AcceptCallback的方法，這個AcceptCallback名稱是我們自己取的
                    // Start an asynchronous socket to listen for connections.                                        
                    gListenerSocket.BeginAccept(new AsyncCallback(AcceptCallback), gListenerSocket);

                    //WaitOne的方法鎖定了執行緒，狀態也是WaitSleepJoin.
                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }

            }
            catch (Exception e)
            {
                throw new Exception("To start listening and to accept an incoming connection attempt failure. ==> " + e);
                //log.Write("StartListening: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages, "-", "StartListening: " + e.Message);
            }
            //Console.WriteLine("\nPress ENTER to continue...");
            //Console.Read();
        }

        public void AcceptCallback(IAsyncResult ar)
        {
            try
            {
                //喚醒多個執行緒,讓進入Waitting回到Running狀態。
                // Signal the main thread to continue.
                allDone.Set();

                //AsyncState 取得符合或包含非同步作業資訊的使用者定義的物件
                // Get the socket that handles the client request.
                Socket listener = (Socket)ar.AsyncState;  // 取得主要的socket, 其中包含使用者定義的物件及handles the client request
                //將主要正在 listening(正在連入的client Socket)轉交給另一個臨時的handlerSocket變數，並且結束接受此一客戶端的連線
                Socket handlerSocket = listener.EndAccept(ar);

                //取得遠端節點的EndPoint
                EndPoint RemoteEP = handlerSocket.RemoteEndPoint;

                // Create the state object.
                StateObject state = new StateObject();
                state.workSocket = handlerSocket;
                // 假設已連線的客戶端要傳送資料時，所指定的回呼函式-ReadCallback，這也是C#中的delagate型別
                // 定義當Socket 接收到資料時要交給哪一個函數處理，這裡定義了BeginReceive()
                // 參數第一個為存放的位置，為byte[]，傳入的資料會置於此陣列中；第二參數為啟始位置、第三參數為一次接收的最大長度；
                // 第四為封包的旗標，例如標記為"廣播封包"之類，
                // 第五參數為開始接受資料時叫用的回呼函式，用AsyncCallback實體化指定ReadCallback為回呼函式，所以當客戶端將資料傳進來時，ReadCallback函式會被叫用；
                // 第六為狀態的參數，這個是用戶自訂，當回呼函式ReadCallback被叫用 時，此參數的值會被傳遞到ReadCallback，其中包含Socket，但是回呼函式仍會曉得是哪一個Socket在傳送資料。
                handlerSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), state);
            }
            catch (Exception e)
            {
                throw new Exception("An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e);
                //log.Write("AcceptCallBack: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "AcceptCallBack: " + e.Message);
            }
        }

        public void ReadCallback(IAsyncResult ar)
        {
            String content = String.Empty;
            string resultData = "";

            //重新取回 state object
            // Retrieve the state object and the handler socket from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.workSocket;

            //string client_ip = handler.RemoteEndPoint.ToString().Split(':')[0];

            try
            {
                //叫用 EndReceive 完成指定的非同步接收作業, 參數asyn (ar) 識別要完成的非同步接收作業，並要從其中擷取最終結果。
                // Read data from the client socket. 
                int bytesRead = handler.EndReceive(ar);

                if (bytesRead > 0)
                {

                    // All data received. Get next message. 
                    // Not all data received. Get more.
                    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);        //new

                    //// There  might be more data, so store the data received so far.
                    ////state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                    //state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead));


                    //// Check for end-of-file tag. If it is not there, read more data.
                    //content = state.sb.ToString();

                    //////////////////////////////

                    //if (content.IndexOf("<EOF>") > -1)
                    //{
                    //    //// Reply to the client.
                    //    //Send(handler, resultData);

                    //    // All data received. Get next message. 
                    //    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);        //new

                    //}
                    //else
                    //{
                    //    // Not all data received. Get more.
                    //    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
                    //}
                }
            }
            catch (SocketException ex)
            {
                throw new Exception("An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + ex);
                //if (ex.ErrorCode == 10054 || ex.ErrorCode == 10053)
                //{
                //    log.Write(client_ip + " has left");
                //    LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.ClientLefted,
                //            client_ip, "Client has left");
                //}
                //else
                //{
                //    //Console.WriteLine(ex);
                //    log.Write("ReadCallBack1: " + ex.Message);  //2013/8/22: Log exception message
                //    LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //            "-", "ReadCallBack1: " + ex.Message);
                //}

                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
            }
            catch (Exception e)
            {
                //log.Write("ReadCallBack2: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "ReadCallBack2: " + e.Message);
            }
        }

        private void Send(Socket handler, String data)
        {
            try
            {
                //log.Write(handler.RemoteEndPoint.ToString().Split(':')[0] + " >> " + data);
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.TransmittedToClient,
                //        handler.RemoteEndPoint.ToString().Split(':')[0], data);

                // Convert the string data to byte data using encoding.
                byte[] byteData = Encoding.UTF8.GetBytes(data);

                // Begin sending the data to the remote device.
                handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
            }
            catch (Exception e)
            {
                //log.Write("Send: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "Send: " + e.Message);
            }
        }

        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket handler = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
            }
            catch (Exception e)
            {
                //log.Write("SendCallBack: " + e.Message);   //2013/8/22: Log exception message
                //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                //        "-", "SendCallBack: " + e.Message);
            }
        }

        //private string ProcessData(StateObject so, string receivingData)
        //{
        //    char[] delimiter = { ';', '\n', '\r' };
        //    string replyClient = "";

        //    string[] param = receivingData.Split(delimiter, StringSplitOptions.RemoveEmptyEntries);

        //    so.faKit.BatteryLinkStatus = GetParameterValue("KitStatus", param);

        //    // Check FA Kit and Battery Pack connectivity status: 1 = good; 2 = no connectivity with battery
        //    if (GetParameterValue("KitStatus", param) == "1")
        //    {
        //        // Check Barcode
        //        if (GetParameterValue("Barcode", param) == "DP1234565".ToLower())
        //        {
        //            replyClient = "1";
        //        }
        //        else //Barcode incorrect or not found in list
        //        {
        //            replyClient = "2";
        //        }
        //    }
        //    else        //KitStatus = 2
        //    {
        //        replyClient = "3";
        //    }

        //    return replyClient;
        //}

        //public string GetParameterValue(string parameter, string[] allData)
        //{
        //    string value = "";

        //    foreach (string data in allData)
        //    {
        //        if (data.StartsWith(parameter, true, null) == true)
        //        {
        //            string[] val = data.Split('=');
        //            value = val[1];
        //            break;
        //        }
        //    }

        //    return value;
        //}

        //private bool ServerCloseHandler(CtrlType sig)
        //{
        //    switch (sig)
        //    {
        //        case CtrlType.CTRL_C_EVENT:
        //        case CtrlType.CTRL_SHUTDOWN_EVENT:
        //        case CtrlType.CTRL_CLOSE_EVENT:
        //            {
        //                log.Write("Server is exited");
        //                log.CloseLogger();
        //                string mailbody = "[Warning] RCSS Server has been exited."
        //                    + Environment.NewLine
        //                    + System.DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss]");
        //                LogHelper.sendingMailBySQL(LogHelper.SendWarningMailAddress, "[Warning] RCSS Server has been exited.", mailbody);
        //                LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.ServerMessages,
        //                        "-", "Server is exited");
        //                return false;
        //            }
        //        case CtrlType.CTRL_LOGOFF_EVENT:
        //        default:
        //            return false;
        //    }
        //}


    }//public class ServerSocket

    // State object for reading client data asynchronously
    public class StateObject
    {
        // Client  socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 1024;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data string.
        public StringBuilder sb = new StringBuilder();
    }
}//namespace LEV_Applications
